1 module unit_threaded.randomized.gen; 2 3 template from(string moduleName) { 4 mixin("import from = " ~ moduleName ~ ";"); 5 } 6 7 /* Return $(D true) if the passed $(D T) is a $(D Gen) struct. 8 9 A $(D Gen!T) is something that implicitly converts to $(D T), has a method 10 called $(D gen) that is accepting a $(D ref Random). 11 12 This module already brings Gens for numeric types, strings and ascii strings. 13 14 If a function needs to be benchmarked that has a parameter of custom type a 15 custom $(D Gen) is required. 16 */ 17 template isGen(T) { 18 static if (is(T : Gen!(S), S...)) 19 enum isGen = true; 20 else 21 enum isGen = false; 22 } 23 24 /// 25 unittest { 26 static assert(!isGen!int); 27 static assert(isGen!(Gen!(int, 0, 10))); 28 } 29 30 private template minimum(T) { 31 import std.traits : isIntegral, isFloatingPoint, isSomeChar; 32 33 static if (isIntegral!T || isSomeChar!T) 34 enum minimum = T.min; 35 else static if (isFloatingPoint!T) 36 enum mininum = T.min_normal; 37 else 38 enum minimum = T.init; 39 } 40 41 private template maximum(T) { 42 import std.traits : isNumeric; 43 44 static if (isNumeric!T) 45 enum maximum = T.max; 46 else 47 enum maximum = T.init; 48 } 49 50 /** A $(D Gen) type that generates numeric values between the values of the 51 template parameter $(D low) and $(D high). 52 */ 53 mixin template GenNumeric(T, T low, T high) { 54 55 import std.random : Random; 56 57 static assert(is(typeof(() { T[] res = frontLoaded(); })), 58 "GenNumeric needs a function frontLoaded returning " ~ T.stringof ~ "[]"); 59 60 alias Value = T; 61 62 T value; 63 64 T gen(ref Random gen) { 65 import std.random : uniform; 66 67 static assert(low <= high); 68 69 this.value = _index < frontLoaded.length ? frontLoaded[_index++] 70 : uniform!("[]")(low, high, gen); 71 72 return this.value; 73 } 74 75 ref T opCall() { 76 return this.value; 77 } 78 79 void toString(scope void delegate(const(char)[]) sink) @trusted { 80 import std.format : formattedWrite; 81 import std.traits : isFloatingPoint; 82 83 static if (isFloatingPoint!T) { 84 static if (low == T.min_normal && high == T.max) { 85 formattedWrite(sink, "'%s'", this.value); 86 } 87 } else static if (low == T.min && high == T.max) { 88 formattedWrite(sink, "'%s'", this.value); 89 } else { 90 formattedWrite(sink, "'%s' low = '%s' high = '%s'", this.value, low, high); 91 } 92 } 93 94 alias opCall this; 95 96 private int _index; 97 } 98 99 /** A $(D Gen) type that generates numeric values between the values of the 100 template parameter $(D low) and $(D high). 101 */ 102 struct Gen(T, T low = minimum!T, T high = maximum!T) 103 if (from!"std.traits".isIntegral!T) { 104 private static T[] frontLoaded() @safe pure nothrow { 105 import std.algorithm : filter; 106 import std.array : array; 107 108 T[] values = [0, 1, T.min, T.max]; 109 return values.filter!(a => a >= low && a <= high).array; 110 } 111 112 mixin GenNumeric!(T, low, high); 113 } 114 115 struct Gen(T, T low = 0, T high = 6.022E23) 116 if (from!"std.traits".isFloatingPoint!T) { 117 private static T[] frontLoaded() @safe pure nothrow { 118 import std.algorithm : filter; 119 import std.array : array; 120 121 T[] values = [0, T.epsilon, T.min_normal, high]; 122 return values.filter!(a => a >= low && a <= high).array; 123 } 124 125 mixin GenNumeric!(T, low, high); 126 } 127 128 /** A $(D Gen) type that generates ASCII strings with a number of 129 characters that is between template parameter $(D low) and $(D high). 130 131 If $(D low) and $(D high) are very close together, this might return 132 values that are too short. They should differ by at least three for 133 char strings, one for wstrings, and zero for dstrings. 134 */ 135 struct Gen(T, size_t low = 0, size_t high = 32) 136 if (from!"std.traits".isSomeString!T) { 137 static const dchar[] charset; 138 import std.random : Random, uniform; 139 140 static immutable size_t numCharsInCharSet; 141 alias Value = T; 142 143 T value; 144 static this() { 145 import std.array : array; 146 import std.uni : unicode; 147 import std.format : format; 148 import std.range : chain, iota; 149 import std.algorithm : map, joiner; 150 import std.conv : to; 151 import std.utf : count; 152 153 charset = chain( // \t and \n 154 iota(0x09, 0x0B), // \r 155 iota(0x0D, 0x0E), // ' ' through '~'; next is DEL 156 iota(0x20, 0x7F), // Vulgar fractions, punctuation, letters with accents, Greek 157 iota(0xA1, 158 0x377), // More Greek 159 iota(0x37A, 0x37F), iota(0x384, 0x38A), iota(0x38C, 160 0x38C), iota(0x38E, 0x3A1), // Greek, Cyrillic, a bit of Armenian 161 iota(0x3A3, 0x52F), // Armenian 162 iota(0x531, 163 0x556), iota(0x559, 0x55F), // Arabic 164 iota(0xFBD3, 0xFD3F), 165 iota(0xFD50, 0xFD8F), iota(0xFD92, 0xFDC7), // Linear B, included because it's a high character set 166 iota(0x1003C, 0x1003D), 167 iota(0x1003F, 0x1004D), iota(0x10050, 0x1005D), iota(0x10080, 168 0x100FA), // Emoji 169 iota(0x1F300, 0x1F6D4)).map!(a => cast(dchar) a).array; 170 numCharsInCharSet = charset.length; 171 } 172 173 T gen(ref Random gen) { 174 static assert(low <= high); 175 import std.range.primitives : ElementType; 176 import std.array : appender; 177 import std.utf : encode; 178 179 if (_index < frontLoaded.length) { 180 value = frontLoaded[_index++]; 181 return value; 182 } 183 184 auto app = appender!T(); 185 app.reserve(high); 186 size_t numElems = uniform!("[]")(low, high, gen); 187 static if ((ElementType!T).sizeof == 1) { 188 char[4] buf; 189 } else static if ((ElementType!T).sizeof == 2) { 190 wchar[2] buf; 191 } else { 192 dchar[1] buf; 193 } 194 195 size_t appLength = 0; 196 while (appLength < numElems) { 197 size_t charIndex = uniform!("[)")(0, charset.length, gen); 198 auto len = encode(buf, charset[charIndex]); 199 appLength += len; 200 if (appLength > high) 201 break; 202 app.put(buf[0 .. len]); 203 } 204 205 this.value = app.data; 206 return this.value; 207 } 208 209 ref T opCall() { 210 return this.value; 211 } 212 213 void toString(scope void delegate(const(char)[]) sink) { 214 import std.format : formattedWrite; 215 216 static if (low == 0 && high == 32) { 217 formattedWrite(sink, "'%s'", this.value); 218 } else { 219 formattedWrite(sink, "'%s' low = '%s' high = '%s'", this.value, low, high); 220 } 221 } 222 223 alias opCall this; 224 225 private: 226 227 int _index; 228 229 T[] frontLoaded() @safe pure nothrow const { 230 import std.algorithm : filter; 231 import std.array : array; 232 233 T[] values = ["", "a", "é"]; 234 return values.filter!(a => a.length >= low && a.length <= high).array; 235 } 236 } 237 238 /// DITTO This random $(D string)s only consisting of ASCII character 239 struct GenASCIIString(size_t low = 1, size_t high = 32) { 240 import std.random : Random; 241 242 static string charSet; 243 static immutable size_t numCharsInCharSet; 244 245 string value; 246 247 static this() { 248 import std.array : array; 249 import std.uni : unicode; 250 import std.format : format; 251 import std.range : chain, iota; 252 import std.algorithm : map, joiner; 253 import std.conv : to; 254 import std.utf : byDchar, count; 255 256 GenASCIIString!(low, high).charSet = to!string(chain(iota(0x21, 0x7E) 257 .map!(a => to!char(cast(dchar) a)).array)); 258 259 GenASCIIString!(low, high).numCharsInCharSet = count(charSet); 260 } 261 262 string gen(ref Random gen) { 263 import std.array : appender; 264 import std.random : uniform; 265 266 if (_index < frontLoaded.length) { 267 value = frontLoaded[_index++]; 268 return value; 269 } 270 271 auto app = appender!string(); 272 app.reserve(high); 273 size_t numElems = uniform!("[]")(low, high, gen); 274 275 for (size_t i = 0; i < numElems; ++i) { 276 size_t toSelect = uniform!("[)")(0, numCharsInCharSet, gen); 277 app.put(charSet[toSelect]); 278 } 279 280 this.value = app.data; 281 return this.value; 282 } 283 284 ref string opCall() { 285 return this.value; 286 } 287 288 void toString(scope void delegate(const(char)[]) sink) { 289 import std.format : formattedWrite; 290 291 static if (low == 0 && high == 32) { 292 formattedWrite(sink, "'%s'", this.value); 293 } else { 294 formattedWrite(sink, "'%s' low = '%s' high = '%s'", this.value, low, high); 295 } 296 } 297 298 alias opCall this; 299 300 private: 301 302 int _index; 303 304 string[] frontLoaded() @safe pure nothrow const { 305 return ["", "a"]; 306 } 307 } 308 309 struct Gen(T, size_t low = 1, size_t high = 1024) 310 if (from!"std.range.primitives".isInputRange!T && !from!"std.traits".isSomeString!T) { 311 312 import std.traits : Unqual, isIntegral, isFloatingPoint; 313 import std.range : ElementType; 314 import std.random : Random; 315 316 alias Value = T; 317 alias E = Unqual!(ElementType!T); 318 319 T value; 320 Gen!E elementGen; 321 322 T gen(ref Random rnd) { 323 value = _index < frontLoaded.length ? frontLoaded[_index++] : genArray(rnd); 324 return value; 325 } 326 327 alias value this; 328 329 private: 330 331 size_t _index; 332 //these values are always generated 333 T[] frontLoaded() @safe nothrow { 334 T[] ret = [[]]; 335 return ret; 336 } 337 338 T genArray(ref Random rnd) { 339 import std.array : appender; 340 import std.random : uniform; 341 342 immutable length = uniform(low, high, rnd); 343 auto app = appender!T; 344 app.reserve(length); 345 foreach (i; 0 .. length) { 346 app.put(elementGen.gen(rnd)); 347 } 348 349 return app.data; 350 } 351 } 352 353 static assert(isGen!(Gen!(int[]))); 354 355 struct Gen(T) if (is(T == bool)) { 356 import std.random : Random; 357 358 bool value; 359 alias value this; 360 361 bool gen(ref Random rnd) @safe { 362 import std.random : uniform; 363 364 value = [false, true][uniform(0, 2, rnd)]; 365 return value; 366 } 367 } 368 369 struct Gen(T, T low = minimum!T, T high = maximum!T) 370 if (from!"std.traits".isSomeChar!T) { 371 private static T[] frontLoaded() @safe pure nothrow { 372 return []; 373 } 374 375 mixin GenNumeric!(T, low, high); 376 } 377 378 private template AggregateTuple(T...) { 379 import unit_threaded.randomized.random : ParameterToGen; 380 import std.meta : staticMap; 381 382 alias AggregateTuple = staticMap!(ParameterToGen, T); 383 } 384 385 struct Gen(T) if (from!"std.traits".isAggregateType!T) { 386 387 import std.traits : Fields; 388 import std.random : Random; 389 390 AggregateTuple!(Fields!T) generators; 391 392 alias Value = T; 393 Value value; 394 395 T gen(ref Random rnd) @safe { 396 static if (is(T == class)) 397 if (value is null) 398 value = new T; 399 400 foreach (i, ref g; generators) { 401 value.tupleof[i] = g.gen(rnd); 402 } 403 404 return value; 405 } 406 407 inout(T) opCall() inout { 408 return this.value; 409 } 410 411 alias opCall this; 412 413 }